/*----------------------------------------------------------------------------------------
*
*  Copyright 2019, Gao Hai Hui, <fromzeropoint@126.com>.  All rights reserved.
*  https://gitee.com/helloworldghh/xos_ffmpeg.git
*  Use of this source code is governed by a MIT license
*  that can be found in the License file.
*
----------------------------------------------------------------------------------------*/
#include "../import/head.h"
#include "../helper_objs/head.h"
#include "../data_struct/pool.h"
#include "../settings/head.h"
#include "../config/head.h"
#include "../tools/head.h"
#include "../impl/head.h"
#include "../msg/head.h"
#include "../xos/head.h"
#include "sdl2_mem.h"

namespace xos_ffmpeg
{

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 
    sdl2_mem::sdl2_mem()
    {
        init_data();
    }

    sdl2_mem::~sdl2_mem()
    {
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 

    int SDLCALL sdl2_mem::static_read_buffer( void * opaque, uint8_t * buf, int buf_size )
    {
        int ret = -1;
        sdl2_mem & obj = *(sdl2_mem *)opaque;
        ret = obj.read_buffer( buf, buf_size );
        if( 0 == ret )
        {
            ret = AVERROR_EOF;
        }
        return ret;
    }

    void SDLCALL sdl2_mem::static_fill_audio( void * udata, Uint8 * stream, int len )
    {
        sdl2_mem & obj = *(sdl2_mem *)udata;
        obj.fill_audio( stream, len );
    }

    int sdl2_mem::static_init()
    {
        int ret = 0;

        if( 0 == ret )
        {
            ret = SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO );
            if( 0 != ret )
            {
                printf( "Could not initialize SDL - %s\n", SDL_GetError() );
            }
        }

        return ret;
    }

    int sdl2_mem::static_term()
    {
        int ret = 0;
        SDL_Quit();
        return ret;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 

    int sdl2_mem::tanslate_audio_fmt()
    {
        int ret = 0;

        xos_box::i_list * pList = mgr::container()->box()->list();
        xos_box::i_list_iter * pIter = m_pAudioList->begin();
        xos_box::i_big_buf * pBigBuf = m_pBigBuf;
        xos_box::i_buf * pSrcBuf = 0;

        SDL_AudioSpec & obtained = m_audio_obtained_spec;
        SDL_AudioSpec & desired = m_audio_desired_spec;
        SDL_AudioCVT cvt = { 0 };

        if( 0 == ret )
        {
            int r = SDL_BuildAudioCVT( &cvt, desired.format, desired.channels, desired.freq, obtained.format, obtained.channels, obtained.freq );
            if( r < 0 )
            {
                printf( "不支持此格式转换: %s\n", SDL_GetError() );
                ret = 1;
            }
        }

        while( ( 0 == ret ) && ( pSrcBuf = (xos_box::i_buf *)pIter->next() ) )
        {
            cvt.buf = (Uint8 *)pBigBuf->get_data( 0, 0, 0, 0 );
            cvt.len = pSrcBuf->get_len( 0 );
            if( cvt.len * cvt.len_mult > pBigBuf->get_buf_size( 0 ) )
            {
                printf( "转换失败: %s\n", "size to long" );
                ret = 1;
                break;
            }
            pBigBuf->reset();
            pBigBuf->add_data( pSrcBuf, true );
            {
                int r = SDL_ConvertAudio( &cvt );
                if( r < 0 )
                {
                    printf( "转换失败: %s\n", SDL_GetError() );
                    ret = 2;
                    break;
                }
            }
            pBigBuf->set_len( cvt.len_cvt );
            pList->add_data( pBigBuf, true );
        }

        if( 0 == ret )
        {
            m_pAudioList->swap( pList );
        }

        xos_stl::release_interface( pIter );
        xos_stl::release_interface( pList );

        return ret;
    }

    int sdl2_mem::sdl_msg_proc( const SDL_Event & event )
    {
        int ret = 0;

        switch( event.type )
        {
        case SDL_WINDOWEVENT:
            {
                SDL_GetWindowSize( screen, &cur_screen_w, &cur_screen_h );
                update( false );
            }
            break;
        case SDL_QUIT:
            {
                m_bRunning = false;
                helper_main::get()->notify( MAIN_QUITTING );
            }
            break;
        }

        return ret;
    }

    int sdl2_mem::read_buffer( uint8_t * buf, int buf_size )
    {
        int ret = -1;
        char * lpszBuf = (char *)buf;
        int nLen = buf_size;
        ret = m_pFileDataList->get_data( lpszBuf, nLen );
        return ret;
    }

    int sdl2_mem::fill_audio( Uint8 * stream, int len )
    {
        int ret = 0;
        SDL_memset( stream, 0, len );
        if( !m_bRunning || ( 0 == m_pAudioList->size() ) )
        {
            return ret;
        }
        xos_box::i_buf * pBuf = mgr::container()->box()->buf();
        char * lpszBuf = pBuf->get_data( 0, 0, 0, 0 );
        int nLen = m_pAudioList->get_data( lpszBuf, len );
        if( nLen > 0 )
        {
            SDL_MixAudioFormat( stream, (Uint8 *)lpszBuf, m_audio_obtained_spec.format, nLen, SDL_MIX_MAXVOLUME );
        }
        pBuf->set_len( nLen );
        m_pAudioList->push_back( pBuf );
        pBuf = 0;
        return ret;
    }

    int sdl2_mem::update( bool bForceUpdate )
    {
        int ret = 0;
        if( !m_bRunning )
        {
            return ret;
        }
        if( ( cur_screen_w != screen_w ) || ( cur_screen_h != screen_h ) )
        {
            screen_w = cur_screen_w;
            screen_h = cur_screen_h;
            bForceUpdate = true;
        }
        if( bForceUpdate )
        {
            //FIX: If window is resize
            SDL_Rect sdlRect = { 0 };
            sdlRect.x = 0;
            sdlRect.y = 0;
            sdlRect.w = screen_w;
            sdlRect.h = screen_h;
            SDL_RenderClear( sdlRenderer );
            SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect );
            SDL_RenderPresent( sdlRenderer );
        }
        return ret;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 

    int sdl2_mem::decode_all()
    {
        int ret_code = 1;
        int ret = 0;

        // 初始化内存输入
        if( 0 == ret )
        {
            int nSize = xos_box::i_buf::BUF_SIZE;
            uint8_t * lpszBuf = (uint8_t*)av_malloc( nSize + AV_INPUT_BUFFER_PADDING_SIZE );
            m_pAvioCtx = avio_alloc_context( lpszBuf, nSize, 0, this, static_read_buffer, NULL, NULL);
            m_pFileFormatCtx = avformat_alloc_context();
            m_pFrameYUV = av_frame_alloc();
            m_pFrame = av_frame_alloc();
            m_pPacket = av_packet_alloc();
        }

        // 打开输入
        if( 0 == ret )
        {
            char file[4096] = { 0 };
            tools::full_path( file, sizeof( file ), m_input_file.c_str() );
            const char * lpszFile = file;
            {
                m_pFileFormatCtx->pb = m_pAvioCtx;
                lpszFile = 0;
            }
            int r = avformat_open_input( &m_pFileFormatCtx, lpszFile, NULL, NULL );
            if( r < 0 )
            {
                printf( "Couldn't open input stream.\n" );
                ret = ret_code++;
            }
        }

        // 获取流信息
        if( 0 == ret )
        {
            int r = avformat_find_stream_info( m_pFileFormatCtx, NULL );
            if( r < 0 )
            {
                printf( "Couldn't get stream info.\n" );
                ret = ret_code++;
            }
        }

        // 查找视频/音频流
        for( unsigned int i = 0; ( 0 == ret ) && ( i < m_pFileFormatCtx->nb_streams ); ++i )
        {
            AVStream * stream = m_pFileFormatCtx->streams[i];
            AVMediaType type = stream->codecpar->codec_type;
            AVCodecID id = stream->codecpar->codec_id;
            if( AVMEDIA_TYPE_VIDEO == type )
            {
                if( -1 == m_nVideoStreamIndex )
                {
                    m_pVideoCodec = avcodec_find_decoder( id );
                    m_nVideoStreamIndex = i;
                }
            }
            else if( AVMEDIA_TYPE_AUDIO == type )
            {
                if( -1 == m_nAudioStreamIndex )
                {
                    m_pAudioCodec = avcodec_find_decoder( id );
                    m_nAudioStreamIndex = i;
                }
            }
        }

        if( 0 == ret )
        {
            if( !m_pVideoCodec )
            {
                printf( "get video codec failed\n" );
                ret = ret_code++;
            }
            if( !m_pAudioCodec )
            {
                printf( "get audio codec failed\n" );
                ret = ret_code++;
            }
        }

        // 初始化视频解码器
        if( 0 == ret )
        {
            m_pVideoCodecCtx = avcodec_alloc_context3( m_pVideoCodec );
            avcodec_parameters_to_context( m_pVideoCodecCtx, m_pFileFormatCtx->streams[m_nVideoStreamIndex]->codecpar );
            int r = avcodec_open2( m_pVideoCodecCtx, m_pVideoCodec, NULL );
            if( r < 0 )
            {
                printf( "failed to open video codec.\n" );
                ret = ret_code++;
            }
        }

        // 初始化音频解码器
        if( 0 == ret )
        {
            m_pAudioCodecCtx = avcodec_alloc_context3( m_pAudioCodec );
            avcodec_parameters_to_context( m_pAudioCodecCtx, m_pFileFormatCtx->streams[m_nAudioStreamIndex]->codecpar );
            int r = avcodec_open2( m_pAudioCodecCtx, m_pAudioCodec, NULL );
            if( r < 0 )
            {
                printf( "failed to open audio codec.\n" );
                ret = ret_code++;
            }
        }

        // 准备视频转换参数
        if( 0 == ret )
        {
            m_pVideoSwsCtx = sws_getContext( m_pVideoCodecCtx->width, m_pVideoCodecCtx->height, m_pVideoCodecCtx->pix_fmt, m_pVideoCodecCtx->width, m_pVideoCodecCtx->height,
                AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL );
            pixel_h = m_pVideoCodecCtx->height;
            pixel_w = m_pVideoCodecCtx->width;
            cur_screen_w = pixel_w;
            cur_screen_h = pixel_h;
            screen_w = pixel_w;
            screen_h = pixel_h;
            pixformat = SDL_PIXELFORMAT_IYUV;
            m_pFrameYUV->height = m_pVideoCodecCtx->height;
            m_pFrameYUV->width = m_pVideoCodecCtx->width;
            m_pFrameYUV->format = AV_PIX_FMT_YUV420P;
            av_frame_get_buffer( m_pFrameYUV, 0 );
        }

        // 准备转换音频格式参数
        if( 0 == ret )
        {
            AVChannelLayout out_ch_layout;
            AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
            //nb_samples: AAC-1024 MP3-1152
            int out_nb_samples = m_pAudioCodecCtx->frame_size;
            int out_sample_rate = 44100;
            int out_channels = 0;
            {
                uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
                av_channel_layout_from_mask( &out_ch_layout, out_channel_layout );
                int nb_channels = out_ch_layout.nb_channels;
                out_channels = nb_channels;
            }
            {
                SDL_AudioSpec & desired = m_audio_desired_spec;
                desired.freq = out_sample_rate;
                desired.format = AUDIO_S16SYS;
                desired.channels = out_channels;
                desired.samples = out_nb_samples;
            }
            AVChannelLayout in_ch_layout;
            {
                uint64_t in_channel_layout = 0;
                av_channel_layout_default( &in_ch_layout, m_pAudioCodecCtx->ch_layout.nb_channels );
                int64_t channel_layout = in_ch_layout.u.mask;   
                in_channel_layout = channel_layout;
            }
            swr_alloc_set_opts2( &m_pAudioSwrCtx, &out_ch_layout, out_sample_fmt, out_sample_rate,
                &in_ch_layout, m_pAudioCodecCtx->sample_fmt, m_pAudioCodecCtx->sample_rate, 0, NULL );
            swr_init( m_pAudioSwrCtx );
            av_channel_layout_uninit( &out_ch_layout );
            av_channel_layout_uninit( &in_ch_layout );
        }

        // 解码
        while( ( 0 == ret ) && ( av_read_frame( m_pFileFormatCtx, m_pPacket ) >= 0 ) )
        {
            AVCodecContext * dec_ctx = 0;
            bool bVideo = false;
            bool bAudio = false;
            if( m_nVideoStreamIndex == m_pPacket->stream_index )
            {
                dec_ctx = m_pVideoCodecCtx;
                bVideo = true;
            }
            else if( m_nAudioStreamIndex == m_pPacket->stream_index )
            {
                dec_ctx = m_pAudioCodecCtx;
                bAudio = true;
            }
            int r = 0;
            if( !dec_ctx )
            {
                r = 1;
            }
            if( ( 0 == r ) && ( avcodec_send_packet( dec_ctx, m_pPacket ) < 0 ) )
            {
                printf( "Error sending packet to decoder\n" );
                r = 1;
            }
            while( ( 0 == r ) && ( avcodec_receive_frame( dec_ctx, m_pFrame ) >= 0 ) )
            {
                if( bVideo )
                {
                    receive_video_frame();
                }
                else
                {
                    receive_audio_frame();
                }
            }
            av_packet_unref( m_pPacket );
        }

        term_decode_objs();

        return ret;
    }

    int sdl2_mem::receive_audio_frame()
    {
        int ret = 0;

        char * lpszBuf = m_pBigBuf->get_data( 0, 0, 0, 0 );
        int nSize = m_pBigBuf->get_buf_size( 0 );
        uint8_t * lpuszBuf = (uint8_t *)lpszBuf;

        // 得到输入和输出音频格式
        if( 0 == ret )
        {
            int out_nb_samples = swr_convert( m_pAudioSwrCtx, &lpuszBuf, nSize, (const uint8_t **)m_pFrame->data, m_pFrame->nb_samples );
            int nSize = av_samples_get_buffer_size( NULL, m_audio_desired_spec.channels, out_nb_samples, AV_SAMPLE_FMT_S16, 1 );
            // av_samples_fill_arrays(out_frame->data, out_frame->linesize, out_buf, out_channels, out_samples, out_fmt, 0);
            m_pAudioList->add_data( lpszBuf, nSize, true );
        }

        return ret;
    }

    int sdl2_mem::receive_video_frame()
    {
        int ret = 0;

        sws_scale( m_pVideoSwsCtx, (const uint8_t * const *)m_pFrame->data, m_pFrame->linesize, 0, m_pVideoCodecCtx->height, m_pFrameYUV->data, m_pFrameYUV->linesize );
        int y_size = m_pVideoCodecCtx->width * m_pVideoCodecCtx->height;
        m_pVideoList->add_data( (const char *)m_pFrameYUV->data[0], y_size / 1, true );   // Y
        m_pVideoList->add_data( (const char *)m_pFrameYUV->data[1], y_size / 4, true );   // U
        m_pVideoList->add_data( (const char *)m_pFrameYUV->data[2], y_size / 4, true );   // V

        return ret;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 

    int sdl2_mem::helper_notify( int nMsg )
    {
        SDL_Event event;
        event.type = nMsg;
        SDL_PushEvent( &event );
        return 0;
    }

    int sdl2_mem::init_data()
    {
        int ret = 0;

        // 文件解码参数
        m_pFileFormatCtx = 0;
        m_nVideoStreamIndex = -1;
        m_nAudioStreamIndex = -1;
        m_pBigBuf = 0;
        m_pAvioBuf = 0;
        m_pAvioCtx = 0;
        m_pPacket = 0;
        m_pFrameYUV = 0;
        m_pFrame = 0;
        m_input_file = "cuc60anniversary_start.mkv";
        //m_input_file = "cuc_ieschool.flv";

        // 视频解码参数
        m_pVideoCodecCtx = 0;
        m_pVideoCodec = 0;
        m_pVideoSwsCtx = 0;

        // 音频解码参数
        m_pAudioCodecCtx = 0;
        m_pAudioCodec = 0;
        m_pAudioSwrCtx = 0;

        // 音频播放参数
        mgr::container()->crt()->memset( &m_audio_obtained_spec, 0, sizeof( m_audio_obtained_spec ) );
        mgr::container()->crt()->memset( &m_audio_desired_spec, 0, sizeof( m_audio_desired_spec ) );
        m_nAudioDeviceId = -1;

        // 公共播放参数
        m_pFileDataList = 0;
        m_pVideoList = 0;
        m_pAudioList = 0;
        m_bRunning = false;

        // 视频播放参数
        screen = 0;
        sdlRenderer = 0;
        sdlTexture = 0;
        buffer = 0;

        cur_screen_w = 500;
        cur_screen_h = 500;
        screen_w = 500;
        screen_h = 500;

        //IYUV: Y + U + V  (3 planes)
        //YV12: Y + V + U  (3 planes)
        pixformat = SDL_PIXELFORMAT_IYUV;
        pixel_w = 0;
        pixel_h = 0;
        bpp = 12;

        return ret;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 

    int sdl2_mem::notify()
    {
        int ret = 0;
        helper_notify( SDL_USEREVENT );
        return ret;
    }

    int sdl2_mem::heart()
    {
        int ret = 0;

        if( !m_bRunning )
        {
            return ret;
        }
        {
            int nLen = pixel_w * pixel_h * bpp / 8;
            char * lpszBuf = (char *)buffer;
            m_pVideoList->get_data( lpszBuf, nLen );
            m_pVideoList->add_data( lpszBuf, nLen, true );
        }
        {
            SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w );
            update( true );
        }

        return ret;
    }

    int sdl2_mem::wait()
    {
        int ret = 0;

        SDL_Event event = { 0 };
        SDL_WaitEvent( &event );

        if( SDL_USEREVENT != event.type )
        {
            sdl_msg_proc( event );
            ret = 1;
        }

        return ret;
    }

    int sdl2_mem::init()
    {
        int ret = 0;

        if( 0 == ret )
        {
            m_pFileDataList = mgr::container()->box()->list();
            m_pVideoList = mgr::container()->box()->list();
            m_pAudioList = mgr::container()->box()->list();
            m_pBigBuf = mgr::container()->box()->big_buf();
            m_pAvioBuf = mgr::container()->box()->buf();
        }

        if( 0 == ret )
        {
            char file[4096] = { 0 };
            tools::full_path( file, sizeof( file ), m_input_file.c_str() );
            ret = m_pFileDataList->load( file );
        }

        if( 0 == ret )
        {
            ret = decode_all();
        }

        if( 0 == ret )
        {
            screen = SDL_CreateWindow( "Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE );
            if( !screen )
            {
                printf( "SDL: could not create window - exiting:%s\n", SDL_GetError() );
                ret = 2;
            }
        }

        if( 0 == ret )
        {
            SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" );
            sdlRenderer = SDL_CreateRenderer( screen, -1, 0 );
            if( !sdlRenderer )
            {
                ret = 4;
            }
        }

        if( 0 == ret )
        {
            sdlTexture = SDL_CreateTexture( sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h );
            if( !sdlTexture )
            {
                ret = 5;
            }
        }

        if( 0 == ret )
        {
            xos_stl::init_array( buffer, pixel_w * pixel_h * bpp / 8 );
            if( !buffer )
            {
                ret = 3;
            }
        }

        if( 0 == ret )
        {
            //SDL_AudioSpec
            SDL_AudioSpec & desired = m_audio_desired_spec;
            desired.callback = static_fill_audio;
            desired.userdata = this;
            m_nAudioDeviceId = SDL_OpenAudioDevice( NULL, 0, &desired, &m_audio_obtained_spec, SDL_AUDIO_ALLOW_ANY_CHANGE );
            if( m_nAudioDeviceId <= 0 )
            {
                printf( "can't open audio = %s\n", SDL_GetError() );
                ret = 1;
            }
            else
            {
                ret = tanslate_audio_fmt();
            }
        }

        if( 0 == ret )
        {
            m_bRunning = true;
            SDL_PauseAudioDevice( m_nAudioDeviceId, 0 );
        }

        return ret;
    }

    int sdl2_mem::term_decode_objs()
    {
        int ret = 0;

        av_packet_free( &m_pPacket );
        av_frame_free( &m_pFrameYUV );
        av_frame_free( &m_pFrame );
        avcodec_free_context( &m_pVideoCodecCtx );
        avcodec_free_context( &m_pAudioCodecCtx );
        avformat_close_input( &m_pFileFormatCtx );
        sws_freeContext( m_pVideoSwsCtx );
        avio_context_free( &m_pAvioCtx );
        swr_free( &m_pAudioSwrCtx );
        m_pVideoSwsCtx = 0;

        return ret;
    }

    int sdl2_mem::term()
    {
        int ret = 0;

        if( m_nAudioDeviceId > 0 )
        {
            SDL_CloseAudioDevice( m_nAudioDeviceId );
            m_nAudioDeviceId = -1;
        }

        xos_stl::release_interface( m_pFileDataList );
        xos_stl::release_interface( m_pVideoList );
        xos_stl::release_interface( m_pAudioList );
        xos_stl::release_interface( m_pAvioBuf );
        xos_stl::release_interface( m_pBigBuf );
        xos_stl::term_array( buffer );
        term_decode_objs();
        init_data();

        return ret;
    }

} // xos_ffmpeg
